#include "nodecache.h"

//genera un id da associare al peer
guint32 generate_node_id() {
	guint32 id=0;
	id = (unsigned int)rand();
	return id;
	
	//to make sure having a big enough rand(), assuming RAND_MAX>=2^16-1
	id << 16;
	id = (unsigned int)rand();
}

//funzione di testing
void printf_nodecache() {
	node *n;
	printf("***NODECACHE***\n");
	for_each_node(n) {
		printf("id:%d:address:%s:ip:%d\tsockets[client:%d|server:%d]\tCONN_TYPE:%d\n",n->id,n->c->address, n->c->listening_port, n->c->sock_c, n->c->sock_s, n->c->type);
	}
	printf("***END***\n");
}

//parsa una l'indirizzo remoto s di un server in ascolto in formato indirizzo:porta e salva le informazioni nel nodo n
void set_node_address(char *s, node *n) {	//assuming a terminated '\0' source string
	char *d;
	d = strchr(s, ':');
	int l = d-s;
	d++;
	
	
	n->c->address = (char*)malloc(sizeof(char)*(l+1));
	strncpy(n->c->address, s, l);
	n->c->listening_port = atoi(d);
}

int _split_address(char **ip_port) {	//assuming a terminated '\0' source string
	char *s;
	s = *ip_port;
	char *d;
	d = strchr(s, ':');
	int l = d-s;
	d++;
	
	
	ip_port = (char*)malloc(sizeof(char)*(l+1));
	strncpy(*ip_port, s, l);
	int port = atoi(d);
	return port;
}

node* make_node(char *ip, int port, size_t id, unsigned int type) {
	n_malloc_zs(tmp, node);
	malloc_zs(tmp->c, connection);
	
	tmp->id = id;
	tmp->c->address = ip;
	tmp->c->listening_port = port;
	tmp->c->type = type;
	
	pthread_mutex_init(&tmp->mtx, NULL);
	pthread_mutex_init(&tmp->c->mtx_c, NULL);
	
	return tmp;
}

//aggiunge un nodo alla nodecache a partire dall'indirizzo remoto ip, dalla porta, dall'id e dal tipo di nodo
node* add_node(char *ip, int port, size_t id, unsigned int type) {
	n_malloc_zs(tmp, node);
	malloc_zs(tmp->c, connection);
	
	tmp->id = id;
	tmp->c->address = ip;
	tmp->c->listening_port = port;
	tmp->c->type = type;
	
	pthread_mutex_init(&tmp->mtx, NULL);
	pthread_mutex_init(&tmp->c->mtx_c, NULL);
	
	list_element* e = addObject(&node_cache, (void*)tmp);
	
	return (node*)e->object;
}

//aggiunge un nodo alla nodecache a partire da una connection
node* add_node_conn(connection * c) {
	n_malloc_zs(tmp, node);
	tmp->c = c;
	
	pthread_mutex_init(&tmp->mtx, NULL);
	pthread_mutex_init(&tmp->c->mtx_c, NULL);

	list_element* e = addObject(&node_cache, (void*)tmp);
	
	return (node*)e->object;
}

void _disconnect_node(node *n) {
	doShutdown(n->c);
	pthread_cancel(n->handler);
}

//elimina un nodo dalla nodecache
void delete_node(node *n) {
	free_s(n->c->address);
	free_s(n->c);
	
	delete_object(&node_cache, n);
	free_s(n);
}

//aggiunge un nodo alla nodecache a partire dall'indirizzo remoto ip in formato indirizzo:porta, dall'id e dal tipo di nodo
node* add_node_boot(char* ip, size_t id, unsigned int type) {
	n_malloc_zs(tmp, node);
	malloc_zs(tmp->c, connection);

	tmp->id = id;
	set_node_address(ip, tmp);
	tmp->c->type = type;

	list_element* e = addObject(&node_cache, (void*)tmp);
	
	return (node*)e->object;
}

//ritorna il nodo con l'id specificato o null se non presente. effettua il lock sul mutex associato al nodo
node* get_node_l(guint32 id) {
	node *n;
	for_each_node(n) {
		lock_node(n);
		if (n->id == id)
			return n;
		unlock_node(n);
	}
	return NULL;
}

int node_is_contained_l(node *n) {
	node *cursor;
	lock_nodecache();
	for_each_node(cursor)
		if(cursor == n)
			return 1;
	return 0;
}

//ritorna il nodo con l'id specificato o null se non presente.
node* get_node_by_id(guint32 id) {
	node *n;
	for_each_node(n)
		if (n->id == id)
			return n;
	
	return NULL;
}

//ritorna il nodo con l'ip specificato o null se non presente.
node* get_node_by_ip(char* ip) {
	node *n;
	for_each_node(n)
			if (strcmp(n->c->address, ip) == 0)
			return n;
	
	return NULL;
}

//ritorna il primo nodo in ascolto o null se non presente.
node* get_a_listener() {
	node *n;
	for_each_node(n){
		if (connection_is_a_listener(n->c))
			return n;
	}
	return NULL;
}

node* get_node_twin(connection *c) {
	node *n;
	for_each_node(n) {
		if(!(strcmp(c->address, n->c->address)==0)
				   && c->type&CONNECTION_TYPE_CONNECT) {	
			printf("[get_node_twin]found:%s:%d\n",c->address,c->listening_port);
			return n;
				   }
	}
	return NULL;
}

#define INCOMPLETE_CONN_THREAD_SCHEDULER 15
void _rem_incomplete_conn(){
	while(1){
		node *n;
		for_each_node(n){
			if (!valid_connection(n->c))
				delete_node(n);
		}
		sleep(INCOMPLETE_CONN_THREAD_SCHEDULER);
	}
}

unsigned short _get_nodecache_actives() {
	unsigned short size;
	node *n;
	for_each_node(n) {
		if(valid_socket(n->c->sock_c) && valid_socket(n->c->sock_s))
			size++;
	}
	
	return size;
}

//ritorna il numero di clients contenuti nella nodecache.
unsigned short get_nodecache_clients() {
	unsigned short size = 0;
	node *n;
	node *listener = get_node_me();
	for_each_node(n) {
		if(valid_socket(n->c->sock_s))
			size++;
	}
	
	return size;
}

//ritorna il numero di clients contenuti nella nodecache. effettua il lock della nodecache.
unsigned short get_nodecache_clients_l() {
	lock_nodecache();
	
	unsigned short size = get_nodecache_clients();
	
	unlock_nodecache();
	
	return size;
}